package academic.concordia.bankofquebec.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import academic.concordia.bankofquebec.bean.utils.BankAccountGenerator;

@Entity
@DiscriminatorColumn(
		name="BankAccount_Data",
		discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue(value="BANK_ACCOUNT")
public abstract class BankAccount implements Serializable{
	
	private static final long serialVersionUID = 1L;
	private String accountNumber;
	private String checkDigit;
	private double balance;
	private BankAccountStatus status;
	protected String type;
	private Client holder;
	private List<Transaction> transactions;
	protected AccountAttributes attributes;
	
	public BankAccount(){
		this.transactions = new ArrayList<Transaction>();
	}
	
	public void createBankAccountNumber(){
		this.accountNumber = BankAccountGenerator.generateAccount(attributes.prefix());
		this.checkDigit = BankAccountGenerator.generateCheckDigit(this.accountNumber);
	}
	
	//Getters and setters
	@Id
	public String getAccountNumber() {
		return accountNumber;
	}
	
	public void setAccountNumber(String accountNumber){
		this.accountNumber = accountNumber;
	}

	public double getBalance() {
		return balance;
	}
	
	public void setBalance(double balance) {
		this.balance = balance;
	}

	public String getCheckDigit() {
		return checkDigit;
	}

	public void setCheckDigit(String checkDigit) {
		this.checkDigit = checkDigit;
	}

	@OneToOne
	public Client getHolder() {
		return holder;
	}

	public void setHolder(Client holder) {
		this.holder = holder;
	}

	@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
	@Fetch(FetchMode.SELECT)
	public List<Transaction> getTransactions() {
		return transactions;
	}

	public void setTransactions(List<Transaction> transactions) {
		this.transactions = transactions;
	}
	
	public void addTransaction(Transaction transaction){
		transactions.add(0, transaction);
	}

	public String getType() {
		return type;
	}

	public AccountAttributes getAttributes() {
		return attributes;
	}

	public void setAttributes(AccountAttributes attributes) {
		this.attributes = attributes;
	}

	public void setType(String type) {
		this.type = type;
	}
	
	@Enumerated(EnumType.STRING)
	public BankAccountStatus getStatus() {
		return status;
	}

	public void setStatus(BankAccountStatus status) {
		this.status = status;
	}

	public Transaction withdraw(double amount){
		Transaction transaction = new Transaction();
		if (amount > this.balance){
			transaction.setStatus(TransactionStatus.REJECTED);
			transaction.setMessage("Insufficient funds");
			return transaction;
		}
		
		this.balance -= amount;
		transaction = new Transaction();
		//transaction.setAffectedAccount(this);
		transaction.setAmount(-1*amount);
		transaction.setTransactionType(TransactionType.WITHDRAWAL);
		transactions.add(transaction);
		
		return transaction;
	}
	
	public Transaction deposit(double amount){
		this.balance += amount;
		Transaction transaction = new Transaction();
		//transaction.setAffectedAccount(this);
		transaction.setAmount(amount);
		transaction.setTransactionType(TransactionType.DEPOSIT);
		transactions.add(transaction);
		return transaction;
	}
	
	public Transaction payment(double amount){
		Transaction transaction = withdraw(amount);
		if (transaction.isApproved()){
			transaction.setTransactionType(TransactionType.BILL_PAYMENT);
			transactions.add(transaction);
		}
		return transaction;
	}
	
	public void revertWithdrawal(Transaction withdrawal){
		this.balance += withdrawal.getAmount();
		for (Transaction transaction : transactions){
			if (transaction.equals(withdrawal)){
				transactions.remove(withdrawal);
				return;
			}
		}
	}
	
	@Transient
	public boolean isTransferAccount(){
		return true;
	}
	
	@Transient
	public boolean isPaymentAccount(){
		return false;
	}
}
